#!/usr/bin/env python2

import sys
import os
import random
import argparse
import string
import socket
import struct
import commands

import time
import itertools
import threading

from urlparse import urlparse

from Ump.common import remote 
from Ump import utils
from Ump import defs
from Ump.common.log import init_info_logger
from Ump.common.config_base import ConfigManager

db_cfg = ConfigManager(defs.DB_CFG_PATH, section='mysql')

LOG = init_info_logger()


def reset_dict_value(dict_name, value):
    return dict.fromkeys(dict_name, value)

def get_default_gateway_ip():
    '''This function will return default route gateway ip address'''
    with open("/proc/net/route") as gateway:
        try:
            for item in gateway:
                fields = item.strip().split()
                if fields[1] != '00000000' or not int(fields[3], 16) & 2:
                    continue
                if fields[7] == '00000000':
                    return socket.inet_ntoa(struct.pack("=L", int(fields[2], 16)))
        except ValueError:
            return None

class SpinnerInfo(object):
    spinner_status = {}
    def __init__(self):
        self.output = ""
        self.name = ""

class ShowSpinner(object):

    def __init__(self, spinner_info):
        self.output = spinner_info.output
        self.name = spinner_info.name
        self.spinner = itertools.cycle("|/~\\")

        self.thread = threading.Thread(target=self.run, args=())
        self.thread.daemon = True
        self.thread.start()

    def run(self):
        time.sleep(.2)
        while SpinnerInfo.spinner_status[self.name]:
                sys.stdout.write("\r %s: ... %s " % (self.output, next(self.spinner)))
                sys.stdout.flush()
                time.sleep(.1)
        #print "\r %s: ... %s" % (self.output, colored("PASS","green"))

def show_spinner(output, name):
    spinner_info = SpinnerInfo()
    spinner_info.output = output
    spinner_info.name = name
    SpinnerInfo.spinner_status = reset_dict_value(SpinnerInfo.spinner_status, False)
    SpinnerInfo.spinner_status[name] = True
    ShowSpinner(spinner_info)
    return SpinnerInfo


def parse_ssh_url(ssh_url):
    """
    :return: ssh_url transfer to object, contain username, password, host
    """

    return urlparse('http://%s'%ssh_url)


is_print = None
def execute_remote(hostname, cmd):
    return remote._exec_remote(hostname, cmd, is_print=is_print, timeout=600)

class DeployHA(object):

    MN_HOSTS = {}
    VIRTUAL_IP_ADDRESS = None
    VRID = None
    ETH_DEV = None
    ETH_ALIAS_NAME = 2
    GATEWAY = None
    args = None
    
    def __init__(self):
        pass 
    
    def update_mn_hosts(self, args):
        host1_info = args.host1_info
        host1_obj = parse_ssh_url(host1_info)

        host2_info = args.host2_info
        host2_obj = parse_ssh_url(host2_info)
        
        host_infos = [host1_obj, host2_obj]
        [self.MN_HOSTS.update({host.hostname:host}) for host in host_infos]

        
    def install_ha_deps(self, host):
        SpinnerInfo = show_spinner('Install Dependencies', 'deps')
        pre_stor_repo = '''
        cat > /etc/yum.repos.d/FusionStorLocal.repo <<EOF
[FusionStor]
name=FusionStor
baseurl=file:///opt/fusionstack-dvd
gpgcheck=0
enabled=0
EOF
        '''
        execute_remote(host, pre_stor_repo)

        pre_install_cmd = '''
        cat > /etc/yum.repos.d/FusionStorHA.repo <<EOF
[FusionStorHA]
name=FusionStorHA
baseurl=file:///opt/fusionstack/fusionstor/packages/ha
gpgcheck=0
enabled=0
EOF
        '''

        install_deps_cmd = 'yum --disablerepo=* --enablerepo=FusionStorHA install -y haproxy keepalived xinetd boost-program-options'
        execute_remote(host, pre_install_cmd)
        execute_remote(host, install_deps_cmd)

        # check MariaDB is support wsrep mode
        self._check_MariaDB(host)


    def _check_MariaDB(self, host):
        #check whether mysql is running
        mysqlshow = execute_remote(host, 'mysqlshow; echo ""')
        mysqlshow = mysqlshow.strip()
        if not mysqlshow:
            self.reinstall_MariaDB(host)
            return 

        show_wresp_cmd = '''mysql -u%s --password="%s" -e"show status like 'wsrep%%'"'''\
                     % ('fusionstor', 'password') 

        wsrep_stdout = None 
        try:
            wsrep_stdout = execute_remote(host, show_wresp_cmd)
        except :
            pass
        
        if not wsrep_stdout:
            self.reinstall_MariaDB(host)
        
    def reinstall_MariaDB(self, host): 
        SpinnerInfo = show_spinner('Install MariaDB', 'mariadb')
        remove_mariadb = 'yum remove -y MariaDB'
        execute_remote(host, remove_mariadb)

        install_mariadb_galera = 'pkill -9 mysql; yum --disablerepo=* --enablerepo=FusionStor -y install MariaDB-Galera-server'
        execute_remote(host, install_mariadb_galera)
        
    
    def configure_galera(self, host):
        galera_cfg_template = '''
        cat > /etc/my.cnf.d/galera.cnf << EOF
[mysqld]
skip-name-resolve=1
character-set-server=utf8
binlog_format=ROW
default-storage-engine=innodb
innodb_autoinc_lock_mode=2
innodb_locks_unsafe_for_binlog=1
max_connections=2048
query_cache_size=0
query_cache_type=0
bind_address= $bind_address
wsrep_provider=/usr/lib64/galera/libgalera_smm.so
wsrep_cluster_name="galera_cluster"
wsrep_cluster_address="gcomm://$wsrep_cluster_address"
wsrep_slave_threads=1
wsrep_certify_nonPK=1
wsrep_max_ws_rows=131072
wsrep_max_ws_size=1073741824
wsrep_debug=0
wsrep_convert_LOCK_to_trx=0
wsrep_retry_autocommit=1
wsrep_auto_increment_control=1
wsrep_drupal_282555_workaround=0
wsrep_causal_reads=0
wsrep_notify_cmd=
wsrep_sst_method=rsync
EOF
        '''

    
        wsrep_cluster_address = ",".join(self.MN_HOSTS.keys())
        bind_address = host
        substitute_kw = {'wsrep_cluster_address':wsrep_cluster_address,'bind_address':bind_address} 
        cmd = string.Template(galera_cfg_template).substitute(substitute_kw)

        execute_remote(host, cmd)

    def configure_xinetd(self, host):
        local_src = "%s/mysql-check" % (defs.CONF_PATH)
        remote_dist = "/etc/xinetd.d/mysql-check"
        remote._put_remote(host, local_src, remote_dist)
            
        template = "if [[ ! $$(cat $services) =~  $mysqlcheck ]];then echo '$mysqlcheck      6033/tcp     #MYSQL status check' >> $services;fi"
        cmd = string.Template(template).substitute(dict(services='/etc/services', mysqlcheck='mysqlcheck'))
        execute_remote(host, cmd)

    def configure_haproxy(self, host):
        haproxy_cfg_template = '''
global

    log         127.0.0.1 local2 emerg alert crit err warning notice info debug

    chroot      /var/lib/haproxy
    pidfile     /var/run/haproxy.pid
    maxconn     4000
    user        haproxy
    group       haproxy
    daemon

    # turn on stats unix socket
    stats socket /var/lib/haproxy/stats

#---------------------------------------------------------------------
# common defaults that all the 'listen' and 'backend' sections will
# use if not designated in their block
#---------------------------------------------------------------------
defaults
    mode                    http
    log                     global
    option                  httplog
    option                  dontlognull
    option http-server-close
    option forwardfor       except 127.0.0.0/8
    option                  redispatch
    retries                 3
    timeout http-request    10s
    timeout queue           1m
    timeout connect         10s
    timeout client          1m
    timeout server          1m
    timeout http-keep-alive 1m
    timeout check           1m
    timeout tunnel          60m
    maxconn                 6000


listen  admin_stats 0.0.0.0:9132
    mode        http
    stats uri   /admin
    stats realm     Global\ statistics
    stats auth  admin:admin

listen  proxy-mysql 0.0.0.0:53306
    mode tcp
    option tcplog
    balance source
    option httpchk OPTIONS * HTTP/1.1\\r\\nHost:\ www
    server $hostname1 $host1:3306 weight 10 check port 6033 inter 3s rise 2 fall 2
    server $hostname2 $host2:3306 backup weight 10 check port 6033 inter 3s rise 2 fall 2
    option tcpka

# dashboard not installed, so haproxy will report error
listen  proxy-ui 0.0.0.0:7070
    mode http
    option  http-server-close
    balance source
    server $hostname1 $host1:8080 weight 10 check inter 3s rise 2 fall 2
    server $hostname2 $host2:8080 weight 10 check inter 3s rise 2 fall 2
    option  tcpka
        '''
        host1, host2 = self.MN_HOSTS.keys()
        hostname1 = execute_remote(host1, 'hostname')
        hostname2 = execute_remote(host2, 'hostname')
        kwargs = dict(host1=host1, host2=host2, hostname1=hostname1.strip(), hostname2=hostname2.strip())
        haproxy_cfg_content = string.Template(haproxy_cfg_template).substitute(kwargs)
        local_src = "/opt/fusionstack/tmp/haproxy.cfg"
        with open(local_src, 'w') as haproxy_cfg:
            haproxy_cfg.write(haproxy_cfg_content)

        remote_dist = '/etc/haproxy/haproxy.cfg'
        remote._put_remote(host, local_src, remote_dist)

    def configure_keepalived(self, host, another_host):
        keepalived_cfg_template = '''
        cat > /etc/keepalived/keepalived.conf << EOF
! Configuration File for keepalived
global_defs {
   router_id HAPROXY_LOAD
}
vrrp_script Monitor_Haproxy {
 script "/usr/local/bin/keepalived-kill.sh"
 interval 2
 weight 5
}
vrrp_script Monitor_FusionStorUI {
 script "/usr/local/bin/keepalived-StandbyfusionstorUI.sh "
 interval 20
 weight 5
}
vrrp_script Monitor_Mariadb_galera {
 script "/usr/local/bin/check_gateway.sh $another_host $gateway"
 interval 30
 weight 5
}

vrrp_instance VI_1 {
    # use the same state with host2, so no master node, recovery will not race to control the vip
   state MASTER
   interface $ethdev
   virtual_router_id $virtual_router_id
   priority 91
   nopreempt
   advert_int 1
   authentication {
      auth_type PASS
      auth_pass cfmdoj9gl5xutjb
   }
   track_script {
      Monitor_Haproxy
      Monitor_FusionStorUI
      Monitor_Mariadb_galera
}
   virtual_ipaddress {
      $virtual_ipaddress/24 dev $ethdev label $ethdev:$eth_alias_name
 }
}
EOF
'''
        master_priority = 102 
        slave_priority = 101 
        second_slave_priority = 100 
        kwargs = dict(
            virtual_router_id=self.VRID,
            virtual_ipaddress=self.VIRTUAL_IP_ADDRESS, 
            priority=master_priority, 
            ethdev=self.ETH_DEV, 
            eth_alias_name=self.ETH_ALIAS_NAME,
            gateway=self.GATEWAY,
            another_host=another_host
        )
        if self.MN_HOSTS.keys().index(host) == 0:
            kwargs.update(dict(virtual_ipaddress=self.VIRTUAL_IP_ADDRESS,priority=master_priority))
        elif self.MN_HOSTS.keys().index(host) == 1:
            kwargs.update(dict(virtual_ipaddress=self.VIRTUAL_IP_ADDRESS, priority=slave_priority))
        elif self.MN_HOSTS.keys().index(host) == 2:
            kwargs.update(dict(virtual_ipaddress=self.VIRTUAL_IP_ADDRESS, priority=second_slave_priority)) 

        keepalived_cfg_cmd = string.Template(keepalived_cfg_template).substitute(kwargs)
        execute_remote(host, keepalived_cfg_cmd)
        


    def deploy_bin_sh(self, host):
        local_src = "%s/keepalived-kill.sh" % (defs.CONF_PATH)
        remote_dist = "/usr/local/bin/keepalived-kill.sh"
        remote._put_remote(host, local_src, remote_dist, ischmod=True)

        mysqlchk_template = '''
        cat > /usr/local/bin/mysqlchk_status.sh << EOF
#!/bin/sh
MYSQL_HOST="$host"
MYSQL_PORT="$port"
MYSQL_USERNAME="$mysql_username"
MYSQL_PASSWORD="$mysql_password"
/usr/bin/mysql -h\$$MYSQL_HOST -u\$$MYSQL_USERNAME -p\$$MYSQL_PASSWORD -e "show databases;" > /dev/null
if [ "\$$?" -eq 0 ]
then
        # mysql is fine, return http 200
        /bin/echo -e "HTTP/1.1 200 OK"
        /bin/echo -e "Content-Type: Content-Type: text/plain"
        /bin/echo -e "MySQL is running."
else
        # mysql is fine, return http 503
        /bin/echo -e "HTTP/1.1 503 Service Unavailable"
        /bin/echo -e "Content-Type: Content-Type: text/plain"
        /bin/echo -e "MySQL is *down*."
fi
EOF
'''
        #local_src = "%s/mysqlchk_status.sh" % (defs.CONF_PATH)
        #remote_dist = "/opt/fusionstack/tmp/mysqlchk_status.sh"
        #remote._put_remote(host, local_src, remote_dist, ischmod=True)

        kwargs = {'host':host, 'port':3306, 'mysql_username':'fusionstor', 'mysql_password':'password'}
        cmd = string.Template(mysqlchk_template).substitute(kwargs)

        execute_remote(host, cmd)
        execute_remote(host, 'chmod a+x /usr/local/bin/mysqlchk_status.sh')
        self._deploy_standby_UI(host)
        self._deploy_check_gateway_script(host)

    def _deploy_standby_UI(self, host):
        standby_template = '''
        cat > /usr/local/bin/keepalived-StandbyfusionstorUI.sh << EOF
#!/bin/bash

now=\$$(date)
echo \$$now>>/root/test.log

ip addr |grep $ethdev:$eth_alias_name
vip_recode=\$$?

ump-dashboard-server --stat |grep 'is running'
ui_recode=\$$?

echo 'vip_recode:'\$$vip_recode
echo 'ui_recode:'\$$ui_recode
if [ \$$vip_recode == 0 ] && [ \$$ui_recode != 0 ];
#if [ \$$vip_recode == 0 ];
then 
    echo 'start fusionstor ui'
    ump-dashboard-server --start
fi

if [ \$$vip_recode != 0 ] && [ \$$ui_recode == 0 ];
then 
    echo 'stop fusionstor ui'
    ump-dashboard-server --stop
    ump-controller-server --stop
fi
EOF
'''
        kwargs = {'ethdev':self.ETH_DEV, 'eth_alias_name':self.ETH_ALIAS_NAME}
        cmd = string.Template(standby_template).substitute(kwargs)
        execute_remote(host, cmd)
        execute_remote(host, 'chmod a+x /usr/local/bin/keepalived-StandbyfusionstorUI.sh')

    def _deploy_check_gateway_script(self, host):
        gateway_template = '''
        cat > /usr/local/bin/check_gateway.sh << EOF
#!/bin/bash
MYSQL_HOST="$host"
MYSQL_PORT="3306"
MYSQL_USERNAME="root"
MYSQL_PASSWORD="$mysql_root_password"
# Checking partner ...
ping -c 4 -w 4 \$$1 > /dev/null 2>&1
if [ \$$? -ne 0 ]; then
    # Checking gateway ...
    ping -c 4 -w 4 \$$2 > /dev/null 2>&1
    if [ \$$? -ne 0 ]; then
        echo "Network ERROR! Kill MySQL NOW!" >> /opt/fusionstack/log/fusionstor/check-network.log
        pgrep -f mysql | xargs kill -9
    else
        echo "Setting the primary of Galera." >> /opt/fusionstack/log/fusionstor/check-network.log
        /usr/bin/mysql  -u\$$MYSQL_USERNAME --password=\$$MYSQL_PASSWORD -e "SET GLOBAL wsrep_provider_options='pc.bootstrap=YES';" > /dev/null
        ump-controller-server --start
    fi
fi
TIMEST=\`date\`
echo \$$TIMEST >> /opt/fusionstack/log/fusionstor/check-network.log
EOF
        '''
        kwargs = {'host':host, 'mysql_root_password':self.args.mysql_root_password}
        cmd = string.Template(gateway_template).substitute(kwargs)
        execute_remote(host, cmd)
        execute_remote(host, 'chmod a+x /usr/local/bin/check_gateway.sh')

    def set_environment(self, host):
        cmd = "sed -i 's/SELINUX\=enforcing/SELINUX\=disabled/g' /etc/selinux/config"
        execute_remote(host, cmd)

        cmd = "iptables -C INPUT -p tcp -m tcp --dport 53306 -j ACCEPT > /dev/null 2>&1 || iptables -I INPUT -p tcp -m tcp --dport 53306 -j ACCEPT; " \
                       "iptables -C INPUT -p tcp -m tcp --dport 4567 -j ACCEPT > /dev/null 2>&1 ||  iptables -I INPUT -p tcp -m tcp --dport 4567 -j ACCEPT ; " \
                       "iptables -C INPUT -p tcp -m tcp --dport 9132 -j ACCEPT > /dev/null 2>&1 ||  iptables -I INPUT -p tcp -m tcp --dport 9132 -j ACCEPT ; " \
                       "iptables -C INPUT -p tcp -m tcp --dport 7070 -j ACCEPT > /dev/null 2>&1 ||  iptables -I INPUT -p tcp -m tcp --dport 7070 -j ACCEPT ; " \
                       "iptables -C INPUT -p tcp -m tcp --dport 6033 -j ACCEPT > /dev/null 2>&1 || iptables -I INPUT -p tcp -m tcp --dport 6033 -j ACCEPT; systemctl reload iptables;echo 'ignore err' "
        execute_remote(host, cmd)

    def service_status(self, host, service, option='restart', enabled=True):
        cmd = 'systemctl %s %s' % (option, service)
        execute_remote(host, cmd) 
        if enabled:
            cmd = 'systemctl enable %s' % (service)
            execute_remote(host, cmd) 
        
    def restart_ha_status(self, host):
        SpinnerInfo = show_spinner('Restart Service', 'ha1')
        self.service_status(host, 'xinetd')
        self.service_status(host, 'haproxy')
        self.service_status(host, 'keepalived')
        #"chkconfig --level 2345 mysql on"

    def _package_fusionstor(self):
        utils._exec_pipe('cd %s && tar -zcf fusionstor.tar.gz fusionstor/ '
                         '--exclude=fusionstor/include/ '
                         '--exclude=fusionstor/bin/ '
                         '--exclude=fusionstor/lib '
                         '--exclude=fusionstor/lib64' % (defs.install_path))
        utils._exec_pipe('cd %s && cat fusionstor/install-fusionstor-bin.sh fusionstor.tar.gz >fusionstor.bin '
                         ';rm -rf fusionstor.tar.gz' %(defs.install_path))

    def install_fusionstor(self, host):
        SpinnerInfo = show_spinner('Install FusionStorUI', 'ui')
        ret, local_hostname, stderr = utils._exec_pipe('hostname', is_raise=True)
        remote_hostname = execute_remote(host, 'hostname')
        is_self =  local_hostname.strip() == remote_hostname.strip()
        if is_self:
            return 'not need fusionstor on self node'

        local_src = '%s/fusionstor.bin' % (defs.install_path)
        remote_dist = '%s/tmp/fusionstor.bin' % (defs.install_path)
        remote._put_remote(host, local_src, remote_dist)
        
        cmd = 'bash %s/tmp/fusionstor.bin' % (defs.install_path)
        execute_remote(host, cmd)

    def sshkey_mn(self, host):
        password = self.MN_HOSTS.get(host).password
        remote.ssh_key(host, password)

    def _set_fusionstor_db(self, host):
        cmd = 'bash /opt/fusionstack/fusionstor/ump/Ump/scripts/deploy_db.sh'
        execute_remote(host, cmd)

        cmd = 'ump-ctrl initdb -u fusionstor -p password --host %s -d fusionstor -f' % (host)
        execute_remote(host, cmd)


    def check_gateway(self):
        # check gw ip is available
        if self.GATEWAY is None:
            default_gateway = get_default_gateway_ip() 
            if default_gateway is None:
                error("Can't get the gateway IP address from system, please check your route table or specific the " \
                      "\"--gateway\" argument")
            else:
                self.GATEWAY = default_gateway

        #debug
        if is_print:
            print "Your Gateway is %s. if not, you need specify the gateway argument" % self.GATEWAY

        (status, output) = commands.getstatusoutput('ping -c 1 %s' % self.GATEWAY)
        if status != 0:
            error("The gateway %s unreachable!" % self.GATEWAY)

    def recover_mariadb(self):
        SpinnerInfo = show_spinner('Start MariaDB', 'start_mariadb')
        for host in self.MN_HOSTS:
            execute_remote(host, '/etc/init.d/mysql stop')
            execute_remote(host, 'pkill -9 mysql 2>&1; echo "OK"> /dev/null')
        
        first_node = True
        for host in self.MN_HOSTS:
            start_cmd = '/etc/init.d/mysql start'
            if first_node:
                start_cmd = "%s --wsrep-new-cluster" % start_cmd
                first_node = False
            print host, start_cmd
            execute_remote(host, start_cmd)
    
    
    def install_rpm_for_hosts(self):
        SpinnerInfo = show_spinner('Package Fusionstor Manage', 'ha')

#        self._package_fusionstor()

        for host in self.MN_HOSTS:
            self.sshkey_mn(host)

        for host in self.MN_HOSTS:
            #install haproxy
            self.install_ha_deps(host)

            self.set_environment(host)
#            self.install_fusionstor(host)


        for host in self.MN_HOSTS:
            #deploy shell
            another_hosts = [x for x in self.MN_HOSTS if x != host]
            another_host = ",".join(another_hosts)
            self.deploy_bin_sh(host)

            #
            self.configure_xinetd(host)
            self.configure_galera(host)
            self.configure_haproxy(host)
            self.configure_keepalived(host, another_host)
            self.restart_ha_status(host)        

        self.recover_mariadb()
        for host in self.MN_HOSTS:
            self._check_mysql_root_password(host)
            self._set_fusionstor_db(host)
        SpinnerInfo.spinner_status = reset_dict_value(SpinnerInfo.spinner_status, False)
        print 'Deploy HA Success'

    def _check_mysql_root_password(self, host):
        mysql_root_password = self.args.mysql_root_password
        check_cmd = '''mysql -uroot --password="%s" -e"show status like 'wsrep%%'"'''\
                     % (mysql_root_password)
        stdout = None 
        try:
            stdout = execute_remote(host, check_cmd)
        except :
            pass
        if not stdout:
            raise Exception("%s :Access denied for user 'root', your mysql root password is not default,"
                            "you can specify the --mysql_root_password argument" % (host))

    def run(self, args):
        global is_print
        is_print = args.debug
        self.VRID = args.vrid or random.randint(20, 200)
        self.ETH_DEV = args.ethdev
        self.GATEWAY = args.gateway
        self.ETH_ALIAS_NAME = args.vip_aliasing
        self.VIRTUAL_IP_ADDRESS = args.vip 
        self.args = args
        if not self.VIRTUAL_IP_ADDRESS:
            raise Exception('virtual ip must be specified')

        self.update_mn_hosts(args)

        self.check_gateway()

        self.install_rpm_for_hosts()
    
    def recover_ha(self, args):
        global is_print
        is_print = True
        rcode, addresses, stderr = utils._exec_pipe(
                                "cat /etc/my.cnf.d/galera.cnf |grep wsrep_cluster_address|awk -F '//' '{print $2}'")

        addresses = addresses.strip().strip('"')
        hosts = addresses.split(',')
        [self.MN_HOSTS.update({host.strip():host}) for host in hosts]
        self.recover_mariadb()

    
if __name__ == '__main__':
    h = [parse_ssh_url('root:mdsmds@192.168.120.73'), parse_ssh_url('root:mdsmds@192.168.120.72')]
    d = DeployHA()
    #d.install_rpm(h)
    d._deploy_standby_UI('192.168.115.137')

